package common.db.dao.hibernate;

import java.io.Serializable;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.logging.log4j.LogManager;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.PropertyValueException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.resource.transaction.spi.TransactionStatus;

import common.db.dao.DaoConstraintException;
import common.db.dao.DaoLockException;
import common.db.dao.DaoPropertyException;

import core.exception.PhysicalException;
import core.exception.ThrowableUtil;

/**
 * バッチ用DAO
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public class MainDao extends BaseDao {

	/** シーケンスクエリキャッシュ */
	private final ConcurrentMap<String, SequenceWork> sequence = new ConcurrentHashMap<>();

	/** セションオブジェクト */
	private Session sess = null;

	/**
	 * コンストラクタ
	 *
	 * @param config コンフィグ
	 */
	protected MainDao(final Config config) {
		super(config);
	}

	/**
	 * コミット処理
	 *
	 */
	@Override
	public final void commit() {
		if (this.sess != null && TransactionStatus.ACTIVE.equals(
						this.sess.getTransaction().getStatus())) {
			flush();
			try {
				this.sess.getTransaction().commit();
				this.sess.clear();
			} catch (final HibernateException ex) {
				ThrowableUtil.error(ex);
				throw new PhysicalException(ex);
			} finally {
				clearSequence();
			}
		}
	}

	/**
	 * ロールバック処理
	 * @return 処理結果
	 */
	@Override
	public final boolean rollback() {
		if (this.sess != null && TransactionStatus.ACTIVE.equals(
						this.sess.getTransaction().getStatus())) {
			try {
				this.sess.getTransaction().rollback();
				this.sess.clear();
				return true;
			} catch (final HibernateException ex) {
				ThrowableUtil.error(ex);
			} finally {
				clearSequence();
			}
		}
		return false;
	}

	/**
	 * フラッシュ処理
	 *
	 * @return フラッシュされた場合 true を返す。対象なしの場合 false を返す。
	 */
	@Override
	public final boolean flush() {
		try {
			if (this.sess != null) {
				this.sess.flush();
				this.sess.clear();
				return true;
			}
			return false;
		} catch (final LockAcquisitionException ex) {
			LogManager.getLogger().info(ex.getMessage());
			throw new DaoLockException(ex, super.isNoWait());
		} catch (final ConstraintViolationException ex) {
			LogManager.getLogger().info(ex.getMessage());
			throw new DaoConstraintException(ex, super.isNoWait());
		} catch (final PropertyValueException ex) {
			// not null例外
			LogManager.getLogger().info(ex.getMessage());
			throw new DaoPropertyException(ex);
		} catch (final JDBCException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		} catch (final HibernateException ex) {
			if (super.isUpdateFailed(ex)) {
				LogManager.getLogger().info(ex.getMessage());
				return false;
			}
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * セションクローズ
	 *
	 */
	@Override
	public final void close() {
		// コミットまたはロールバック済みなら処理されない。
		rollback();

		if (this.sess != null) {
			try {
				this.sess.close();
			} catch (final HibernateException e) {
				LogManager.getLogger().warn(e.getMessage(), e);
			} finally {
				this.sess = null;
				clearSequence();
			}
		}
	}

	/**
	 * @see common.db.dao.Dao#sequence(java.lang.String)
	 */
	@Override
	public final long sequence(final String name) {
		SequenceWork sw = this.sequence.get(name);
		if (sw == null) {
			sw = new SequenceWork(getSequenceNextValString(name));
			if (this.sequence.putIfAbsent(name, sw) != null) {
				sw.close();
				sw = this.sequence.get(name);
			}
		}

		beginTransaction();
		getSession().doWork(sw);
		return sw.getSequence();
	}

	/**
	 * シーケンスマップクリア
	 */
	private void clearSequence() {
		for (final Entry<String, SequenceWork> me : this.sequence.entrySet()) {
			me.getValue().close();
		}
		this.sequence.clear();
	}

	/**
	 * セッション取得
	 *
	 * @return セションオブジェクト
	 */
	@Override
	protected final Session getSession() {
		if (this.sess == null) {
			this.sess = super.getSessionFactory().openSession();
		}

		return this.sess;
	}

	/**
	 * トランザクション開始
	 *
	 * @return トランザクション
	 */
	@Override
	protected final Transaction beginTransaction() {
		Transaction tran = getSession().getTransaction();
		if (!TransactionStatus.ACTIVE.equals(tran.getStatus())) {
			tran.begin();
		}
		return tran;
	}

	/**
	 * フラッシュ処理
	 *
	 * @param session セションオブジェクト
	 */
	@Override
	protected final void flushSession(final Session session, final Serializable item) {
		return;
	}
}
